# -*- coding: utf-8 -*-

import importlib
import sys
import time
from collections import deque
from datetime import datetime
from functools import partial
import datetime as dt

# 其他
import numpy as np
import pandas as pd
from PyQt6 import QtCore
from PyQt6.QtCore import Qt
# import pyqtgraph as pg
from PyQt6 import QtWidgets, QtGui
from PyQt6.QtGui import QFont
from PyQt6.QtWidgets import QApplication
# from pyqtgraph.Point import Point

importlib.reload(sys)
# sys.setdefaultencoding('utf8')
# 字符串转换
# ---------------------------------------------------------------------------------------
try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    def _fromUtf8(s):
        return s
from .crosshair import Crosshair
from .key_wraper import *
from vnpy.chart.const import *
# 每日最多Tick数，每秒2根，白天4小时+夜盘3+2.5小时
MAX_TICKS = 2 * 60 * 60 * (4 + 5.5)


class TickWidget(KeyWraper):
    """用于Tick线图"""

    def onRClick(self, pos):
        super().onRClick(pos)

    # 是否完成了历史数据的读取
    initCompleted = False

    # ----------------------------------------------------------------------
    def __init__(self, parent=None, data=None, fields_name=None, graph_type=GRAPH_TYPE_LINE):
        """Constructor"""

        self.parent = parent
        super(TickWidget, self).__init__(parent)
        self.main_graph_type = graph_type

        # 当前序号
        self.x_center = 0  # 下标中间
        self.visible_count = MAX_TICKS  # 显示的数据长度
        self.graph_count = 1  # 图的数量
        self.other_graph = []  # 主图外的其它图列表

        # 缓存数据
        self.data = []
        self.data_len = 2 * 60 * 60 * 95  # 每秒2个Tick*60秒*60分*9.5小时（白天4小时+晚上5.5小时）
        self.fields_name = fields_name  # data 的字段名称

        self.indi_dic = {}  # 当前技术指标字典：key:指标名称，value：指标值序列

        # 所有信号图
        self.allColor = deque(['blue', 'green', 'yellow', 'white', 'red'])
        self.sigData = {}
        self.sigColor = {}
        self.sigPlots = {}

        # 初始化完成
        self.initCompleted = False

        self.popup_menu = None

        # 调用函数
        self.initUi()
        time.sleep(1)
        if data is not None and not data.empty:
            self.convert_dataframe_local(data)

    # ----------------------------------------------------------------------
    #  初始化相关
    # ----------------------------------------------------------------------
    def initUi(self):
        """初始化界面"""
        self.initCompleted = False
        self.grid = QtWidgets.QGridLayout()
        self.setWindowTitle('')
        # 主图
        self.pg_main_plot_widget = pg.PlotWidget()
        # 界面布局
        self.main_layout = pg.GraphicsLayout(border=(100, 100, 100))
        self.main_layout.setContentsMargins(10, 10, 10, 10)
        self.main_layout.setSpacing(0)
        self.main_layout.setBorder(color=(255, 255, 255, 255), width=0.8)
        self.main_layout.setZValue(0)

        self.main_title = self.main_layout.addLabel('')
        self.pg_main_plot_widget.setCentralItem(self.main_layout)
        # 设置横坐标
        xdict = {}
        self.axis_x = DatetimeAxisX(xdict, orientation='bottom')

        # 初始化主图
        self.init_main_plot()

        # 注册十字光标
        self.crosshair = Crosshair(self.pg_main_plot_widget, self, sync_num=2)
        # 设置界面

        self.grid.addWidget(self.pg_main_plot_widget)
        self.setLayout(self.grid)
        # self.setLayout(self.vb)
        #
        self.init_popup_menu()

        # 初始化完成

        self.initCompleted = True

    def init_popup_menu(self):
        self.popup_menu = QtWidgets.QMenu(self)
        # 周期选择
        select_period_act = QtGui.QAction('周期选择', self)
        select_period_act.triggered.connect(self.select_period_act_triggered)
        self.popup_menu.addAction(select_period_act)
        #
        insert_indicator_act = QtGui.QAction('插入指标', self)
        insert_indicator_act.triggered.connect(self.insert_indicator_act_triggered)
        self.popup_menu.addAction(insert_indicator_act)

    def select_period_act_triggered(self):
        """周期选择"""

    def insert_indicator_act_triggered(self):
        """"""
        print(QtGui.QCursor.pos())

    def contextMenuEvent(self, event):
        """右键点击事件"""

        self.popup_menu.popup(QtGui.QCursor.pos())

    def on_data_changed(self, event):
        """当数据发生改变时发生"""

    def make_plot_item(self, name):
        """生成PlotItem对象"""
        vb = CustomViewBox()
        plotItem = pg.PlotItem(viewBox=vb, name=name, axisItems={'bottom': self.axis_x})
        plotItem.setMenuEnabled(False)
        plotItem.setClipToView(True)
        plotItem.hideAxis('left')
        plotItem.showAxis('right')
        plotItem.setDownsampling(mode='peak')
        plotItem.setRange(xRange=(0, 1), yRange=(0, 1))
        plotItem.getAxis('right').setWidth(60)
        plotItem.getAxis('right').setStyle(tickFont=QFont("Roman times", 10, QFont.Bold))
        plotItem.getAxis('right').setPen(color=(255, 255, 255, 255), width=0.8)
        plotItem.showGrid(True, True)
        plotItem.hideButtons()
        return plotItem

    # ----------------------------------------------------------------------
    def init_main_plot(self):
        """初始化主图"""
        self.pw_main_item = self.make_plot_item('PlotMain')
        # self.pw_main_item = self.main_layout.addPlot()

        self.main_curve = self.pw_main_item.plot()  # CandlestickItem(self.data)
        self.pw_main_item.addItem(self.main_curve)

        # self.pw_main_item.hideAxis('bottom')

        self.main_layout.nextRow()
        self.main_layout.addItem(self.pw_main_item)
        # 初始化成交量子图
        self.init_vol_plot()

    def init_vol_plot(self):
        """初始化成交量子图"""
        self.pw_vol = self.make_plot_item('PlotVol')
        # self.pw_vol = self.main_layout.addPlot()
        self.volume_bar = self.pw_vol.plot()
        self.pw_vol.addItem(self.volume_bar)
        self.pw_vol.setMaximumHeight(150)
        self.pw_vol.hideAxis('bottom')
        self.pw_vol.setXLink('PlotMain')

        self.main_layout.nextRow()
        self.main_layout.addItem(self.pw_vol)

    def init_plot_indi(self):
        """初始化技术指标子图"""
        self.pw_ta = self.make_plot_item('plot_indi')
        self.curve_ta = self.pw_ta.plot()
        self.pw_ta.setXLink('PlotKL')
        # self.pw_ta.hideAxis('bottom')
        self.main_layout.nextRow()
        self.main_layout.addItem(self.pw_ta)

    # ----------------------------------------------------------------------
    #  画图相关
    # ----------------------------------------------------------------------
    def plot_vol(self, redraw=False, xmin=0, xmax=-1):
        """重画柱状图"""
        if self.initCompleted:
            # print(self.listVol[xmin:xmax])
            self.volume_bar.setData(y=self.listVol[xmin:xmax])
            self.pw_vol.replot()

    # ----------------------------------------------------------------------
    def plot_main(self, redraw=False, xmin=0, xmax=-1):
        """重画折线图"""
        if self.initCompleted:
            # self.main_curve = pg.PlotDataItem()
            self.main_curve.setData(y=self.listTick[xmin:xmax])
            # self.main_curve.addItem(self.curveOI1)
            # self.main_curve.generatePicture(self.listBar[xmin:xmax], redraw)  # 画 线

    def add_line(self, data, field_name='', graph_area_no=0):
        """增加折线"""

    # ----------------------------------------------------------------------
    def addSig(self, sig):
        """新增信号图"""
        if sig in self.sigPlots:
            self.pw_main_item.removeItem(self.sigPlots[sig])
        self.sigPlots[sig] = self.pw_main_item.plot()
        self.sigColor[sig] = self.allColor[0]
        self.allColor.append(self.allColor.popleft())

    # ----------------------------------------------------------------------
    def showSig(self, datas):
        """刷新信号图"""
        for sig in self.sigPlots:
            self.sigData[sig] = datas[sig]
        [self.sigPlots[sig].setData(datas[sig], pen=self.sigColor[sig][0], name=sig) \
         for sig in self.sigPlots]  # if sig in datas]

    # ----------------------------------------------------------------------
    def plotMark(self):
        """显示开平仓信号"""
        # 检查是否有数据
        if len(self.data) == 0:
            return
        for arrow in self.arrows:
            self.pw_main_item.removeItem(arrow)
        # 画买卖信号
        for i in range(len(self.listSig)):
            # 无信号
            if self.listSig[i] == 0:
                continue
            # 买信号
            elif self.listSig[i] > 0:
                arrow = pg.ArrowItem(pos=(i, self.data[i]['lastPrice']), angle=90, brush=(255, 0, 0))

            # 卖信号
            elif self.listSig[i] < 0:
                arrow = pg.ArrowItem(pos=(i, self.data[i]['high']), angle=-90, brush=(0, 255, 0))
            self.pw_main_item.addItem(arrow)
            self.arrows.append(arrow)

    # ----------------------------------------------------------------------
    def updateAll(self):
        """
        手动更新所有K线图形，K线播放模式下需要
        """
        datas = self.data
        self.volume_bar.update()
        self.main_curve.update()

        def update(view, low, high):
            vRange = view.viewRange()
            xmin = max(0, int(vRange[0][0]))
            xmax = max(0, int(vRange[0][1]))
            xmax = min(xmax, len(datas))
            if len(datas) > 0 and xmax > xmin:
                ymin = min(datas[xmin:xmax][low])
                ymax = max(datas[xmin:xmax][high])
                view.setRange(yRange=(ymin, ymax))
            else:
                view.setRange(yRange=(0, 1))

        update(self.pw_main_item.getViewBox(), 'lastPrice', 'lastPrice')
        update(self.pw_vol.getViewBox(), 'volume', 'volume')

    # ----------------------------------------------------------------------
    def plotAll(self, redraw=True, xMin=0, xMax=-1):
        """
        重画所有界面
        redraw ：False=重画最后一根K线; True=重画所有
        xMin,xMax : 数据范围
        """
        xMax = len(self.data) if xMax < 0 else xMax
        self.visible_count = xMax - xMin
        self.x_center = int((xMax + xMin) / 2)
        self.pw_main_item.setLimits(xMin=xMin, xMax=xMax)
        self.plot_main(redraw, xMin, xMax)
        self.plot_vol(redraw, xMin, xMax)
        self.refresh()

    # ----------------------------------------------------------------------
    def refresh(self):
        """
        刷新三个子图的现实范围
        """
        data = self.data
        minutes = int(self.visible_count / 2)
        xmin = max(0, self.x_center - minutes)
        xmax = xmin + 2 * minutes
        # print('refresh: ', xmin, xmax)

        self.pw_main_item.setRange(xRange=(xmin, xmax))

    # ----------------------------------------------------------------------
    #  快捷键相关
    # ----------------------------------------------------------------------
    def onNxt(self):
        """跳转到下一个开平仓点"""
        if len(self.listSig) > 0 and not self.x_center is None:
            datalen = len(self.listSig)
            self.x_center += 1
            while self.x_center < datalen and self.listSig[self.x_center] == 0:
                self.x_center += 1
            self.refresh()
            x = self.x_center
            y = self.data[x]['lastPrice']
            self.crosshair.signal.emit((x, y))

    # ----------------------------------------------------------------------
    def onPre(self):
        """跳转到上一个开平仓点"""
        if len(self.listSig) > 0 and not self.x_center is None:
            self.x_center -= 1
            while self.x_center > 0 and self.listSig[self.x_center] == 0:
                self.x_center -= 1
            self.refresh()
            x = self.x_center
            y = self.data[x]['lastPrice']
            self.crosshair.signal.emit((x, y))

    # ----------------------------------------------------------------------
    def onDown(self):
        """放大显示区间"""
        self.visible_count = min(len(self.data), int(self.visible_count * 1.2) + 1)
        self.refresh()
        if len(self.data) > 0:
            try:
                x = self.x_center - self.visible_count / 2 + 2 if int(
                    self.crosshair.xAxis) < self.x_center - self.visible_count / 2 + 2 else int(self.crosshair.xAxis)
                x = self.x_center + self.visible_count / 2 - 2 if x > self.x_center + self.visible_count / 2 - 2 else x
                y = self.data[x]['lastPrice']
                self.crosshair.signal.emit((x, y))
            except:
                pass

    # ----------------------------------------------------------------------
    def onUp(self):
        """缩小显示区间"""
        self.visible_count = max(3, int(self.visible_count / 1.2) - 1)
        self.refresh()
        if len(self.data) > 0:
            try:
                x = self.x_center - self.visible_count / 2 + 2 if int(
                    self.crosshair.xAxis) < self.x_center - self.visible_count / 2 + 2 else int(self.crosshair.xAxis)
                x = self.x_center + self.visible_count / 2 - 2 if x > self.x_center + self.visible_count / 2 - 2 else x
                # print('onUp:x=', x)
                y = self.data[x]['lastPrice']
                # print('onUp:x,y=',x,y)
                self.crosshair.signal.emit((x, y))
            except:
                pass

    # ----------------------------------------------------------------------
    def onLeft(self):
        """向左移动"""
        if len(self.data) > 0 and int(self.crosshair.xAxis) > 2:
            try:
                x = int(self.crosshair.xAxis) - 1
                y = self.data[x]['lastPrice']
                if x <= self.x_center - self.visible_count / 2 + 2 and self.x_center > 1:
                    self.x_center -= 1
                    self.refresh()
                self.crosshair.signal.emit((x, y))
            except:
                pass

    # ----------------------------------------------------------------------
    def onRight(self):
        """向右移动"""
        if len(self.data) > 0 and int(self.crosshair.xAxis) < len(self.data) - 1:
            try:
                x = int(self.crosshair.xAxis) + 1
                y = self.data[x]['lastPrice']
                if x >= self.x_center + int(self.visible_count / 2) - 2:
                    self.x_center += 1
                    self.refresh()
                self.crosshair.signal.emit((x, y))
            except:
                pass

    # ----------------------------------------------------------------------
    # 界面回调相关
    # ----------------------------------------------------------------------
    def onPaint(self):
        """界面刷新回调"""
        view = self.pw_main_item.getViewBox()
        vRange = view.viewRange()
        xmin = max(0, int(vRange[0][0]))
        xmax = max(0, int(vRange[0][1]))
        self.x_center = int((xmin + xmax) / 2) + 1

    def onLDoubleClick(self, pos):
        if self.crosshair.visible:
            self.crosshair.hide()
        else:
            self.crosshair.show()
            self.crosshair.plotDataInfo(pos.x())

    # ----------------------------------------------------------------------
    def resignData(self, data):
        """更新数据，用于Y坐标自适应"""
        self.crosshair.data = data

        def viewXRangeChanged(low, high, self):
            vRange = self.viewRange()
            xmin = max(0, int(vRange[0][0]))
            xmax = max(0, int(vRange[0][1]))
            xmax = min(xmax, len(data))
            if len(data) > 0 and xmax > xmin:
                ymin = min(data[xmin:xmax][low])
                ymax = max(data[xmin:xmax][high])
                self.setRange(yRange=(ymin, ymax))
            else:
                self.setRange(yRange=(0, 1))

        view = self.pw_main_item.getViewBox()
        view.sigXRangeChanged.connect(partial(viewXRangeChanged, 'LastPrice', 'LastPrice'))

        view = self.pw_vol.getViewBox()
        view.sigXRangeChanged.connect(partial(viewXRangeChanged, 'Volume', 'Volume'))

        # view = self.pwOI.getViewBox()
        # view.sigXRangeChanged.connect(partial(viewXRangeChanged, 'openInterest', 'openInterest'))

    # ----------------------------------------------------------------------
    # 数据相关
    # ----------------------------------------------------------------------
    def clear_data(self):
        """清空数据"""
        # 清空数据，重新画图
        self.time_index = []
        self.listBar = []
        self.listVol = []
        self.listLow = []
        self.listHigh = []
        self.listOpenInterest = []
        self.listSig = []
        self.sigData = {}
        self.arrows = []
        self.data = None

    # ----------------------------------------------------------------------
    def updateSig(self, sig):
        """刷新买卖信号"""
        self.listSig = sig
        self.plotMark()

    # ----------------------------------------------------------------------
    def on_tick(self, tick, nWindow=20):
        """
        新增Tick数据
        nWindow : 最大数据窗口
        """
        # 是否需要更新K线
        newBar = False if len(self.data) > 0 and tick.datetime == self.data[-1].datetime else True
        nrecords = len(self.data) if newBar else len(self.data) - 1
        tick.openInterest = np.random.randint(0,
                                              3) if tick.openInterest == np.inf or tick.openInterest == -np.inf else tick.openInterest
        recordVol = (nrecords, tick.volume, 0, 0, tick.volume) if tick.close_price < tick.open_price else (
            nrecords, 0, tick.volume, 0, tick.volume)
        if newBar and any(self.data):
            self.data.resize(nrecords + 1, refcheck=0)
            # self.listBar.resize(nrecords + 1, refcheck=0)
            # self.listVol.resize(nrecords + 1, refcheck=0)
        elif any(self.data):
            self.listLow.pop()
            self.listHigh.pop()
            self.listOpenInterest.pop()
        if any(self.data):
            self.data[-1] = (tick.datetime, tick.open_price, tick.close_price, tick.low_price, tick.high_price, tick.volume, tick.openInterest)
            self.listBar[-1] = (nrecords, tick.open_price, tick.close_price, tick.low_price, tick.high_price)
            self.listVol[-1] = recordVol
        else:
            self.data = np.rec.array(
                [(datetime, tick.open_price, tick.close_price, tick.low_price, tick.high_price, tick.volume, tick.openInterest)], \
                names=('datetime', 'open', 'close', 'low', 'high', 'volume', 'openInterest'))
            self.listBar = np.rec.array([(nrecords, tick.open_price, tick.close_price, tick.low_price, tick.high_price)], \
                                        names=('datetime', 'open', 'close', 'low', 'high'))
            self.listVol = np.rec.array([recordVol], names=('datetime', 'open', 'close', 'low', 'high'))
            self.resignData(self.data)
        self.axis_x.update_xdict({nrecords: tick.datetime})
        self.listLow.append(tick.low_price)
        self.listHigh.append(tick.high_price)
        self.listOpenInterest.append(tick.openInterest)
        xMax = nrecords + 1
        xMin = max(0, nrecords - nWindow)
        if not newBar:
            self.updateAll()
        self.plotAll(False, xMin, xMax)
        self.crosshair.signal.emit((None, None))

    def convert_dataframe_local(self, df):
        """
        将pandas.DataFrame数据格式转变为做图可用的
        datas : 数据格式，cols : fields = {'symbol': 1, 'datetime': 1, 'lastPrice': 1, 'volume': 1, 'askPrice1': 1,
              'askVolume1': 1, 'bidPrice1': 1, 'bidVolume1': 1, 'openInterest': 1, 'date': 1, 'time': 1,'TradingDay':1}
              数据按照日期时间由小到大排序
        """
        # 设置中心点时间
        self.x_center = 0

        # 绑定数据，更新横坐标映射，更新Y轴自适应函数，更新十字光标映射
        df['time_int'] = np.array(range(len(df.index)))
        self.data = df[list(df.columns)].to_records(False)  # , 'openInterest'
        self.fields_name = list(df.columns)
        self.listTick = list(df['LastPrice'])
        self.listVol = list(df['Volume'])
        self.axis_x.xdict = {}
        xdict = dict(enumerate(df.index.tolist()))

        self.axis_x.update_xdict(xdict)
        self.resignData(self.data)

        # 调用画图函数
        self.plotAll(True, 0, len(self.data))
